package org.eclipse.iota.arithmetics;

import static org.eclipse.iota.util.IotaReplaceUtil.insertParent;
import static org.eclipse.iota.util.IotaReplaceUtil.replace;

import org.eclipse.iota.IotaPackage;
import org.eclipse.iota.evaluator.IotaConstantEvaluator;
import org.eclipse.iota.evaluator.IotaEvaluator.EvaluationException;
import org.eclipse.iota.model.Addition;
import org.eclipse.iota.model.Constant;
import org.eclipse.iota.model.Division;
import org.eclipse.iota.model.Exponentiation;
import org.eclipse.iota.model.Expression;
import org.eclipse.iota.model.Multiplication;
import org.eclipse.iota.model.Negation;
import org.eclipse.iota.model.Operation;
import org.eclipse.iota.util.IotaCopyUtil;
import org.eclipse.iota.util.IotaEqualityUtil;
import org.eclipse.iota.util.IotaReplaceUtil;

/**
 * Utility method for arithmetical operations.
 * @author Christian Krause
 * @generated NOT
 */
public class IotaOperationExecutor {
	
	/**
	 * Add an expression to another expression.
	 * @param base Expression to be changed.
	 * @param argument Expression to be added.
	 */
	public static Expression add(Expression base, Expression argument) {
		
		// Check if the argument is already contained in another object.
		argument = checkArgument(argument);
		
		// Argument = 0 ?
		if (isZero(argument)) return base;
		
		// Base = 0 ?
		if (isZero(base)) return replace(base, argument);
		
		// Check if both expressions are constants.
		if (base instanceof Constant && argument instanceof Constant) {
			return ((Constant) base).add((Constant) argument);
		}
		
		// Check if the argument is an addition:
		if (argument instanceof Addition) {
			Expression result = base;
			for (Expression operand : ((Addition) argument).getOperands()) {
				result = add(result, operand);
			}
			return result;
		}
		
		// Check if it is an addition or create it if not:
		Addition addition;
		if (base instanceof Addition) {
			addition = (Addition) base;
		} else {
			addition = insertParent(base, new Addition(), IotaPackage.eINSTANCE.getMultiOperation_Operands());
		}
		
		// Does the base of the argument exist already in the addition?
		Expression b1 = IotaFactorExtractor.extractBase(argument);
		for (int i=0; i<addition.getOperands().size(); i++) {
			
			// Extract base of argument and current operand.
			Expression operand = addition.getOperands().get(i);
			Expression b2 = IotaFactorExtractor.extractBase(operand);
			
			// Compare argument's base with the one of the current operand:
			if (IotaEqualityUtil.equals(b1, b2)) {
				
				// Simply add the two factors:
				Constant factor = IotaFactorExtractor.extractFactor(argument);
				factor.add(IotaFactorExtractor.extractFactor(operand));
				
				// And replace the operand:
				addition.getOperands().set(i, multiply(factor,b1));
				return addition;
			}
		}
		
		// Otherwise just add a new summand:
		addition.getOperands().add(argument);
		return addition;
		
	}
	
	/**
	 * Negate an expression.
	 * @param base Expression to be negated.
	 */
	public static Expression negate(Expression base) {
		
		// Negative constant?
		if (base instanceof Constant) {
			return ((Constant) base).negate();
		}
		
		// Already a negation?
		if (base instanceof Negation) {
			return replace(base, ((Negation) base).getOperand());
		}
		
		// Insert a new negation.
		return insertParent(base, new Negation(), IotaPackage.eINSTANCE.getUnaryOperation_Operand());
		
	}
	
	/**
	 * Subtract an expression from another expression.
	 * @param base Expression to be changed.
	 * @param argument Expression to be added.
	 */
	public static Expression subtract(Expression base, Expression argument) {
		return add(base, negate(argument));
	}
	
	/**
	 * Multiply an expression with another expression.
	 * @param base Expression to be changed.
	 * @param argument Expression to be multiplied.
	 */
	public static Expression multiply(Expression base, Expression argument) {
		
		// Check if the argument is already contained in another object:
		argument = checkArgument(argument);
		
		// Check for trivial cases:
		if (isOne(argument) || isZero(base)) return base;
		if (isOne(base) || isZero(argument)) return replace(base, argument);
		if (isMinusOne(argument)) return negate(base);
		if (isMinusOne(base)) return replace(base, negate(argument));
		
		// Check if the argument is a multiplication:
		if (argument instanceof Multiplication) {
			Expression result = base;
			for (Expression operand : ((Multiplication) argument).getOperands()) {
				result = multiply(result, operand);
			}
			return result;
		}
		
		// Check if the base is a division.
		if (base instanceof Division) {
			Division div = (Division) base;
			if (IotaEqualityUtil.equals(div.getRight(), argument)) {	
				return replace(div, div.getLeft());
			}
		}
		
		// Check if it is a multiplication and create it if not:
		Multiplication multiplication;
		if (base instanceof Multiplication) {
			multiplication = (Multiplication) base;
		} else {
			multiplication = insertParent(base, new Multiplication(), IotaPackage.eINSTANCE.getMultiOperation_Operands());
		}
				
		// Can we reuse one of the existing operands?
		Expression b1 = IotaExponentExtractor.extractBase(argument);
		for (int i=0; i<multiplication.getOperands().size(); i++) {
			Expression operand = multiplication.getOperands().get(i);			
			
			// Check if both expressions are constants:
			if (operand instanceof Constant && argument instanceof Constant) {
				((Constant) operand).multiply((Constant) argument);
				return multiplication;
			}
			
			// Compare argument's base with the one of the current operand:
			Expression b2 = IotaExponentExtractor.extractBase(operand);
			if (IotaEqualityUtil.equals(b1, b2)) {
				
				// Add the exponents:
				Expression e1 = IotaExponentExtractor.extractExponent(argument);
				Expression e2 = IotaExponentExtractor.extractExponent(operand);
				Expression exponent = add(e1,e2);
				
				// And replace the operand:
				multiplication.getOperands().set(i, pow(b1,exponent));
				return multiplication;
			}
		}
		
		// Constant are added in the front of the list:
		if (argument instanceof Constant) {
			multiplication.getOperands().add(0,argument);
		} else {
			multiplication.getOperands().add(argument);		
		}
		
		return multiplication;
	}
	
	/**
	 * Divide an expression by another expression.
	 * @param base Expression to be changed.
	 * @param argument Expression to be divided by.
	 */
	public static Expression divide(Expression base, Expression argument) {
		
		// Check if the argument is already contained in another object.
		argument = checkArgument(argument);
		
		// Divisor = 1 ?
		if (isOne(argument)) return base;
		
		// Check if both are the same.
		if (IotaEqualityUtil.equals(base, argument)) {
			return replace(base, new Constant(1));
		}
		
		// Check if both expressions are constants.
		if (base instanceof Constant && argument instanceof Constant) {
			Constant c1 = ((Constant) base).copy();
			Constant c2 = ((Constant) argument).copy();
			if (c1.mod(c2.abs()).isZero()) {
				return ((Constant) base).divide(c2);
			}
		}
		
		// Is the base already a division?
		if (base instanceof Division) {
			Expression right1 = ((Division) base).getRight();
			multiply(right1, argument);
			return base;
		}
		
		// Is the argument another division?
		if (argument instanceof Division) {
			Expression left2 = ((Division) argument).getLeft();
			Expression right2 = ((Division) argument).getRight();
			return divide(multiply(base,right2), left2);
		}
		
		// Create the new division object.
		Division division = new Division();
		division.setRight(argument);
		return insertParent(base, division, IotaPackage.eINSTANCE.getBinaryOperation_Left());
		
	}
		
	/**
	 * Take the power of an expression based on a given exponent.
	 * @param base Expression to be changed.
	 * @param exponent Exponent expression.
	 */
	public static Expression pow(Expression base, Expression exponent) {
		
		// Check if the exponent is already contained in another object.
		exponent = checkArgument(exponent);
		
		// Trivial exponent ?
		if (isOne(exponent)) return base;
		if (isZero(exponent)) return replace(base, new Constant(1));
		
		// Base and exponent both constants?
		if (base instanceof Constant && exponent instanceof Constant) {			
			
			// Use evaluator to be on the safe side.
			IotaConstantEvaluator evaluator = new IotaConstantEvaluator();
			try {
				Exponentiation test = new Exponentiation();
				test.setLeft(IotaCopyUtil.copy(base));
				test.setRight(exponent);
				Constant result = evaluator.evaluate(test);
				return IotaReplaceUtil.replace(base, result);
			} catch (EvaluationException e) {
				// Ignore error. The expression cannot be simplified in this case.
			}
			
		}
		
		// Create the new exponentiation object.
		Exponentiation result = new Exponentiation();
		result.setRight(exponent);
		return insertParent(base, result, IotaPackage.eINSTANCE.getBinaryOperation_Left());
		
	}
	
	/**
	 * Apply an operation.
	 * @param base Base expression.
	 * @param argument Argument expression.
	 * @param operation Operation type.
	 * @return The result of the operation.
	 */
	public static Expression apply(Expression base, Expression argument, Operation operation) {
		
		if (operation instanceof Addition) {
			return add(base, argument);
		}
		if (operation instanceof Multiplication) {
			return multiply(base, argument);
		}
		if (operation instanceof Division) {
			return divide(base, argument);
		}
		if (operation instanceof Exponentiation) {
			return pow(base, argument);
		}
		if (operation instanceof Negation) {
			return negate(base);
		}
		throw new RuntimeException("Unknown operation type: " + operation.eClass().getName());
		
	}
	
	/**
	 * Check if an expression is the constant 0.
	 */
	public static boolean isZero(Expression exp) {
		return (exp instanceof Constant) && (((Constant) exp).isZero());
	}

	/**
	 * Check if an expression is the constant 1.
	 */
	public static boolean isOne(Expression exp) {
		return (exp instanceof Constant) && (((Constant) exp).isOne());
	}

	/**
	 * Check if an expression is the constant -1.
	 */
	public static boolean isMinusOne(Expression exp) {
		return (exp instanceof Constant) && (((Constant) exp).isMinusOne());
	}

	
	// ----------------------- //
	// INTERNAL HELPER METHODS //
	// ----------------------- //

	/*
	 * Private helper method that checks if an expression is contained
	 * already in another object.
	 */
	private static Expression checkArgument(Expression expression) {
		if (expression.eContainer()!=null) {
			return IotaCopyUtil.copy(expression);
		} else {
			return expression;
		}
	}
	
}
